﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using DBSchemaViewer.BackEnd.Entities;
using DBSchemaViewer.FrontEnd.DiagramUserControls.CustomEvents;
using DBSchemaViewer.FrontEnd.DiagramUserControls.Interfaces;

namespace DBSchemaViewer.FrontEnd.DiagramUserControls
{
    public partial class CtlDBTable : UserControl, ISelectable
    {
        #region custom_events 
       
        /// <summary>
        /// If the user press left button down on the header of the table control the object is moved
        /// by dragging and dropping, we need to keep this flag in order to avoid sending to the
        /// parent canvas mouse move and left button up events when is not needed (no initial drag performed).
        /// </summary>
        bool _isDraggingTable = false;

        /// <summary>
        /// This flag will tell us if the object has been selected
        /// </summary>
        bool _isObjectSelected = false;

        /// <summary>
        /// Delegate and event, LeftButtonDown on a shape (title header) / field, propage to the parent
        /// canvas
        /// </summary>
        /// <param name="o"></param>
        /// <param name="e"></param>
        public delegate void LButtonDownBubblingEventHandler(object o, TableCtlMouseOperationBubblingArgs e);
        public event LButtonDownBubblingEventHandler LButtonDownBubbled;


        /// <summary>
        /// Delegate and event, LeftButtonUp on a field, propage to the parent
        /// canvas
        /// </summary>
        /// <param name="o"></param>
        /// <param name="e"></param>
        public delegate void LButtonUpBubblingEventHandler(object o, TableCtlMouseOperationBubblingArgs e);
        public event LButtonUpBubblingEventHandler LButtonUpBubbled;


        #endregion //custom_events

        EntityTable _entityTable_info = null;

        public EntityTable EntityTable_info
        {
            get { return _entityTable_info; }
            set { 
                   _entityTable_info = value;
                   tbTitleHeaderText.Text = _entityTable_info.Name;

                   dgFields.ItemsSource = _entityTable_info.Fields;
                   //dgFields.ApplyTemplate();
                   RecalculateColumnsWidth();
                   ResizeControlToFitContent();
                }
        }


        public CtlDBTable()
        {
            InitializeComponent();
        }


        /// <summary>
        /// 
        /// </summary>
        private void RecalculateColumnsWidth()
        {
            // [this code now doesn't work, recheck later if we add horizontal resizing features]
            // We should just apply the new widht to all the control and children
            // Deffer this to RTM and evaluate if it's worthy          
            /*double totalWidth = dgFields.Width;
            double currWidth = 0.0;

            currWidth += (dgFields.Columns[0].Visibility != Visibility.Visible) ? 0.0 : dgFields.Columns[0].Width.Value;
            currWidth += (dgFields.Columns[2].Visibility != Visibility.Visible) ? 0.0 : dgFields.Columns[2].Width.Value;

            double FieldNameWidth = totalWidth - currWidth;

            dgFields.Columns[1].Width = new DataGridLength(FieldNameWidth - 26);
             */
        }

        public Canvas HeadingCanvas()
        {
            return this.cvTitleHeader;
        }

        /// <summary>
        /// Left button down on the table header (move table), quite funny stuff, this control
        /// is only handled at this level, but mouse move and left button up can be directly 
        /// handle on the parent canvas.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Heading_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            // Let's keep it simple... we already have in the root canvas
            // machinery to transform the mouse coordinate to the table related
            // one, let's send a message with this info and let the main view
            // poll for the field clicked            
            if (LButtonDownBubbled != null)
            {
                FireLButtonDownBubbledCustomEvent(e, TableCtlMouseOperationBubblingArgs.MouseOperationCommandsEnum.DRAG_TABLE, -1);
                //Point mousePosition = new Point(e.GetPosition(null).X, e.GetPosition(null).Y);
                //LButtonDownBubbled(this, new MouseOperationBubblingArgs(mousePosition, MouseOperationBubblingArgs.MouseOperationCommandsEnum.CREATE_RELATION, -1));
                _isDraggingTable = true;
                CaptureMouse();
            }            
        }

        /// <summary>
        /// We are going to allow create relations via drag and drop on table fields
        /// (LButtonDown, drag on relation origin, LButtonUp, drop on relation destination).
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FieldTextBoxColumn_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (LButtonDownBubbled != null)
            {
                this.Cursor = Cursors.Hand;

                TextBlock txField = sender as TextBlock;

                // Create relation by dragging and dropping:
                //    - From the control, we know the ID of the table
                //    - From the param + DataContext we can retrieve the ID of the field to relate
                EntityTableFieldEntry currField = (EntityTableFieldEntry)txField.DataContext;

                //int RelationOriginTableID = this._entityTable_info.InternalID;
                int relationOriginFieldID = currField.InternalID;

                FireLButtonDownBubbledCustomEvent(e, TableCtlMouseOperationBubblingArgs.MouseOperationCommandsEnum.CREATE_RELATION, relationOriginFieldID);
            }
        }

        /// <summary>
        /// We are going to allow create relations via drag and drop on table fields
        /// (LButtonDown, drag on relation origin, LButtonUp, drop on relation destination).
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FieldTextBoxColumn_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (LButtonUpBubbled != null)
            {

                this.Cursor = Cursors.Arrow;

                TextBlock txField = sender as TextBlock;

                // Create relation by dragging and dropping:
                //    - From the control, we know the ID of the table
                //    - From the param + DataContext we can retrieve the ID of the field to relate
                EntityTableFieldEntry currField = (EntityTableFieldEntry)txField.DataContext;


                //int RelationDestinationTableID = this._entityTable_info.InternalID;
                int RelationDestinationFieldID = currField.InternalID;

                FireLButtonUpBubbledCustomEvent(e, TableCtlMouseOperationBubblingArgs.MouseOperationCommandsEnum.CREATE_RELATION, RelationDestinationFieldID);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="mousePoint"></param>
        /// <returns></returns>
        public int GetFieldIDFromMouseHit(Point mousePoint)
        {            
            int fieldID = -1;
            int icurrIndex = 0;
            

            System.Collections.Generic.List<System.Windows.UIElement> hits = (System.Collections.Generic.List<System.Windows.UIElement>)VisualTreeHelper.FindElementsInHostCoordinates(mousePoint, this);

            // 1. LOOP THROUH ALL ELEMENTS HIT
            // Search for all the hits
            while (fieldID == -1 && icurrIndex < hits.Count)
            {
                // 2. OUR COLUMNS ARE ONLY TEXTBLOCK
                // From here transform all hits to text Block                
                // Check the x:name?
                // We can cast to the relation entity, and                 
                TextBlock column = hits[icurrIndex] as TextBlock;

                if (column != null)
                {
                    // 3. Check just that the could be valid columns
                    if (column.Name == "txFieldName" || column.Name == "txKeyInfo")
                    {
                        // 4. Check that the field ID belongs to the one stored in our table (could be an overlapped table)
                        EntityTableFieldEntry currField = (EntityTableFieldEntry) column.DataContext;

                        if (currField != null)
                        {
                            //_entityTable_info.Fields[0].InternalID == currField.InternalID

                            fieldID = (_entityTable_info.Fields.FirstOrDefault(p => p.InternalID == currField.InternalID) != null) ? currField.InternalID : -1;                                                                      
                        }
                    }
                }

                icurrIndex++;
            }

            return fieldID;
        }


        /// <summary>
        /// We are giong to fire this event in several places, this method is a helper just to provide a single
        /// entry point and void repeating code everywhere
        /// </summary>
        /// <param name="mousePosition"></param>
        /// <param name="command"></param>
        /// <param name="fieldID">Just used for the CREATE_RELATION command if we are executing another param just
        /// inform it with whatever dummy param, e.g.: -1</param>
        private void FireLButtonDownBubbledCustomEvent(MouseButtonEventArgs e, TableCtlMouseOperationBubblingArgs.MouseOperationCommandsEnum command, int fieldID)
        {
            Point mousePosition = new Point(e.GetPosition(null).X, e.GetPosition(null).Y);
            LButtonDownBubbled(this, new TableCtlMouseOperationBubblingArgs(mousePosition, command, fieldID));
        }

        /// <summary>
        /// We are giong to fire this event in several places, this method is a helper just to provide a single
        /// entry point and void repeating code everywhere
        /// </summary>
        /// <param name="mousePosition"></param>
        /// <param name="command"></param>
        /// <param name="fieldID">Just used for the CREATE_RELATION command if we are executing another param just
        /// inform it with whatever dummy param, e.g.: -1</param>
        private void FireLButtonUpBubbledCustomEvent(MouseButtonEventArgs e, TableCtlMouseOperationBubblingArgs.MouseOperationCommandsEnum command, int fieldID)
        {
            Point mousePosition = new Point(e.GetPosition(null).X, e.GetPosition(null).Y);
            LButtonUpBubbled(this, new TableCtlMouseOperationBubblingArgs(mousePosition, command, fieldID));
        }

        /// <summary>
        /// Right now is quite simple, return the width of the table, but maybe in the future the table widht
        /// for us could be an smaller subset of the control.
        /// </summary>
        /// <returns></returns>
        public double getTableWidth()
        {
            return this.Width;
        }


        /// <summary>
        /// 
        /// </summary>
        private void ResizeControlToFitContent()
        {
            EntityTableFieldEntry lastField = (_entityTable_info.Fields.Count == 0) ? null : _entityTable_info.Fields.Last();

            if (lastField != null)
            {
                double minimumHeight = GetFieldYOffset(lastField.InternalID);
                
                // sum up the last row
                minimumHeight += dgFields.RowHeight;
                bdBorder.Height = minimumHeight;

                LayoutRoot.Height = minimumHeight;
                grFields.Height = minimumHeight - cvTitleHeader.Height - 10;
                grFields.RowDefinitions[0].Height = new GridLength(grFields.Height - 5);
                dgFields.Height = grFields.RowDefinitions[0].Height.Value;
                
                
            }
        }

        /// <summary>
        /// Calculate the Y Offset for a given field shown inside the table.
        /// 
        /// IMPORTANT: This mehtod trust that the order of the associated datasource (list)
        /// maps with the order of the grid rows. Maybe in the future Silverlight offers
        /// some sorted view, then we should change the list to iterate to the sorted
        /// view.
        /// </summary>
        /// <param name="FieldName"></param>
        /// <returns></returns>
        public double GetFieldYOffset(int fieldInternalID)
        {
            double yOffset = 0;

            // DO NOT FORGET TO CHANGE += to = + ... when scaling you can find issues with decimal values

            // First let's position the offset into the beginning of the datagrid showing the columns
            yOffset += (double)grFields.GetValue(Canvas.TopProperty);

            // Let's see if this value is ok and then try to get it from the XAML
            // +5? white offset between header and grid :-( 
            // In the autosize grid this doesn't happen, use that.
            // No need to worry about this is the Border size or something like that
            // we could just substract Height
            yOffset += bdBorder.BorderThickness.Bottom + 5;
            //yOffset += bdBorder.Height - grFields.Height;


            // IMPORTANT: This mehtod trust that the order of the associated datasource (list)
            // maps with the order of the grid rows. Maybe in the future Silverlight offers
            // some sorted view, then we should change the list to iterate to the sorted
            // view.
            if (_entityTable_info.Fields.Count > 0)
            {
                
                // We need to add at least the header
                // Header Row Height is 22
                yOffset += dgFields.RowHeight;

                // Then we need to center in the field the connector
                yOffset += (dgFields.RowHeight / 2);

                // Now let's see in which position it's located
                int position = 0;
                int maxCount = _entityTable_info.Fields.Count;

                // Row Height is 22, BUT we need to add the grid line pixel
                // so the right Row height is 23 (RowHeight + 1).
                // beware here if someday the grid line height changes or it's remove
                double singleRowHeight = dgFields.RowHeight + 1;

                while (position < maxCount && _entityTable_info.Fields[position].InternalID != fieldInternalID)
                {
                    yOffset += singleRowHeight;
                    position++;
                }
            }

            return yOffset;
        }


        #region ISelectable Members

        /// <summary>
        /// Return the flag that indicates whether the object is selected or not
        /// </summary>
        public bool IsSelected
        {
            get { return _isObjectSelected; }
        }

        /// <summary>
        /// Select the object:
        /// 
        ///   - On the UI side, color the border as red.
        ///   - On the data side, set the isObjectSelected flag to true
        /// </summary>
        public void Select()
        {
            bdBorder.BorderBrush = new SolidColorBrush(Colors.Red);
            _isObjectSelected = true; 
        }

        /// <summary>
        /// Unselect the object:
        /// 
        ///   - On the UI side, color the border as red.
        ///   - On the data side, set the isObjectSelected flag to true
        /// </summary>
        public void unSelect()
        {
            
            bdBorder.BorderBrush = new SolidColorBrush(Colors.Black);
            _isObjectSelected = false;

        }

        #endregion

    }
}
